package net.cassite.daf4j.jpa;

import net.cassite.daf4j.*;
import net.cassite.daf4j.ds.AroundParser;
import net.cassite.daf4j.ds.DSUtils;
import net.cassite.daf4j.ds.ObjectResolver;
import net.cassite.daf4j.ds.ParserPacket;
import net.cassite.daf4j.util.ConstantMap;
import net.cassite.daf4j.util.Location;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Around型解释器,将获取信息转化为JPQL
 */
public class JPQLAroundParser implements AroundParser<JPQLContext, String, EntityManager> {
        private static final String aliasPrefix = "var";

        private ParserPacket<JPQLContext, String, EntityManager> parserPacket;

        /**
         * 直接在manager中执行find方法
         *
         * @param manager EntityManager对象
         * @param aClass  要查询的类型
         * @param o       主键
         * @param <En>    要查询的类型
         * @return 查询结果
         * @throws Exception 由jpa抛出的异常
         */
        @Override
        public <En> En find(EntityManager manager, Class<En> aClass, Object o) throws Exception {
                return manager.find(aClass, o);
        }

        /**
         * 构造JPQLContext
         *
         * @param manager        EntityManager对象
         * @param o              要查询的实体
         * @param where          条件子句
         * @param queryParameter 查询参数
         * @return 生成的上下文
         * @throws Exception 由jpa抛出的异常
         */
        @Override
        public JPQLContext initiateList(EntityManager manager, Object o, Where where, QueryParameter queryParameter) throws Exception {
                JPQLContext context = new JPQLContext(manager, o, aliasPrefix);
                context.NoDistinctOrAlreadyDone = queryParameter == null || !queryParameter.parameters.containsKey(QueryParameterTypes.distinct);
                return context;
        }

        /**
         * 生成完整SELECT子句
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingList(JPQLContext jpqlContext) throws Exception {
                String alias = jpqlContext.aliasMap.get(new Location(null));
                jpqlContext.frontQueries = new StringBuilder("SELECT ");
                if (!jpqlContext.NoDistinctOrAlreadyDone) {
                        jpqlContext.frontQueries.append("DISTINCT ");
                }
                jpqlContext.frontQueries.append(alias);
                jpqlContext.selectNonAggregationAliases.add(alias);
                return jpqlContext;
        }

        /**
         * 执行todoList后连接frontQuery到generalJPQL后返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 由jpa抛出的异常
         */
        @Override
        public JPQLContext afterParsingList(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPQLGeneration) {
                        todo.todo(jpqlContext);
                }
                jpqlContext.generalJPQL.insert(0, jpqlContext.frontQueries);
                return jpqlContext;
        }

        /**
         * 根据generalJPQL生成Query,执行每一条JPQLToDo,填入常量,执行jpaQuery.getResultList()
         *
         * @param jpqlContext 上下文
         * @return 执行结果
         * @throws Exception jpa抛出的异常
         */
        @Override
        public List<?> executeList(JPQLContext jpqlContext) throws Exception {
                jpqlContext.jpaQuery = jpqlContext.manager.createQuery(jpqlContext.generalJPQL.toString());
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPAQueryGeneration) {
                        todo.todo(jpqlContext);
                }
                // CONSTANTS
                for (Integer i : jpqlContext.constantMap.keySet()) {
                        jpqlContext.jpaQuery.setParameter(i, jpqlContext.constantMap.get(i));
                }
                return jpqlContext.jpaQuery.getResultList();
        }

        /**
         * 默认构造JPQLContext
         *
         * @param manager                 EntityManager对象
         * @param o                       要查询的实体
         * @param where                   查询条件
         * @param queryParameterWithFocus 带focus的查询参数
         * @return 上下文
         * @throws Exception 可能抛出的异常
         */
        @Override
        public JPQLContext initiateProjection(EntityManager manager, Object o, Where where, QueryParameterWithFocus queryParameterWithFocus) throws Exception {
                JPQLContext context = new JPQLContext(manager, o, aliasPrefix);
                context.NoDistinctOrAlreadyDone = !queryParameterWithFocus.parameters.containsKey(QueryParameterTypes.distinct);
                context.focusMap = queryParameterWithFocus.focusMap;
                return context;
        }

        /**
         * 不执行操作直接返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingProjection(JPQLContext jpqlContext) throws Exception {
                return jpqlContext;
        }

        /**
         * 执行todoList,插入select子句
         *
         * @param jpqlContext 上下文
         * @return 查询结果
         * @throws Exception jpa抛出的异常
         */
        @Override
        public JPQLContext afterParsingProjection(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPQLGeneration) {
                        todo.todo(jpqlContext);
                }
                jpqlContext.generalJPQL.insert(0, jpqlContext.frontQueries);
                return jpqlContext;
        }

        /**
         * 执行TODOList,放入常量,执行查询,并将结果转化为LIST[MAP]
         *
         * @param jpqlContext 上下文
         * @return 查询结果
         * @throws Exception jpa抛出的异常
         */
        @Override
        public List<Map<String, Object>> executeProjection(JPQLContext jpqlContext) throws Exception {
                // TODOList
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPAQueryGeneration) {
                        todo.todo(jpqlContext);
                }
                jpqlContext.jpaQuery = jpqlContext.manager.createQuery(jpqlContext.generalJPQL.toString());
                // CONSTANTS
                for (Integer i : jpqlContext.constantMap.keySet()) {
                        jpqlContext.jpaQuery.setParameter(i, jpqlContext.constantMap.get(i));
                }
                // LIST[OBJCET[]] TO LIST[MAP]
                List<?> resultList = jpqlContext.jpaQuery.getResultList();
                List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(resultList.size());
                for (Object res : resultList) {
                        Map<String, Object> map = new LinkedHashMap<String, Object>();
                        if (res.getClass().isArray()) {
                                int i = 0;
                                for (String alias : jpqlContext.focusMap.values()) {
                                        map.put(alias, ((Object[]) res)[i]);
                                        ++i;
                                }
                        } else {
                                map.put(jpqlContext.focusMap.values().iterator().next(), res);
                        }
                        list.add(map);
                }
                for (Integer i : jpqlContext.constantMap.keySet()) {
                        jpqlContext.jpaQuery.setParameter(i, jpqlContext.constantMap.get(i));
                }
                return list;
        }

        /**
         * 默认构造JPQLContext
         *
         * @param manager       EntityManager对象
         * @param o             更新目标
         * @param where         更新条件
         * @param updateEntries 更新操作
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext initiateUpdate(EntityManager manager, Object o, Where where, UpdateEntry[] updateEntries) throws Exception {
                return new JPQLContext(manager, o, aliasPrefix);
        }

        /**
         * 直接返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingUpdate(JPQLContext jpqlContext) throws Exception {
                return jpqlContext;
        }

        /**
         * 执行todo后返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext afterParsingUpdate(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPQLGeneration) {
                        todo.todo(jpqlContext);
                }
                return jpqlContext;
        }

        /**
         * 执行todo,放入常量,并在query上执行update
         *
         * @param jpqlContext 上下文
         * @throws Exception jpa抛出的异常
         */
        @Override
        public void executeUpdate(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPAQueryGeneration) {
                        todo.todo(jpqlContext);
                }
                jpqlContext.jpaQuery = jpqlContext.manager.createQuery(jpqlContext.generalJPQL.toString());
                for (Integer i : jpqlContext.constantMap.keySet()) {
                        jpqlContext.jpaQuery.setParameter(i, jpqlContext.constantMap.get(i));
                }
                jpqlContext.jpaQuery.executeUpdate();
        }

        /**
         * 默认构造JPQLContext
         *
         * @param manager EntityManager对象
         * @param o       删除目标
         * @param where   删除条件
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext initiateRemove(EntityManager manager, Object o, Where where) throws Exception {
                return new JPQLContext(manager, o, aliasPrefix);
        }

        /**
         * 直接返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingRemove(JPQLContext jpqlContext) throws Exception {
                return jpqlContext;
        }

        /**
         * 执行todo后返回上下文
         *
         * @param jpqlContext 上下文
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext afterParsingRemove(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPQLGeneration) {
                        todo.todo(jpqlContext);
                }
                return jpqlContext;
        }

        /**
         * 执行todo,放入常量,并在query上执行remove
         *
         * @param jpqlContext 上下文
         * @throws Exception jpa抛出的异常
         */
        @Override
        public void executeRemove(JPQLContext jpqlContext) throws Exception {
                for (JPQLToDo<JPQLContext> todo : jpqlContext.toDoAfterJPAQueryGeneration) {
                        todo.todo(jpqlContext);
                }
                jpqlContext.jpaQuery = jpqlContext.manager.createQuery("DELETE " + jpqlContext.generalJPQL.toString());
                for (Integer i : jpqlContext.constantMap.keySet()) {
                        jpqlContext.jpaQuery.setParameter(i, jpqlContext.constantMap.get(i));
                }
                jpqlContext.jpaQuery.executeUpdate();
        }

        /**
         * 直接对数组每一个对象执行manager.persist
         *
         * @param manager EntityManager对象
         * @param objects 要持久化的对象
         * @throws Exception jpa抛出的异常
         */
        @Override
        public void save(EntityManager manager, Object[] objects) throws Exception {
                for (Object o : objects) {
                        manager.persist(o);
                }
        }

        /**
         * 生成FROM子句(不包括JOIN),若where完全应当放在having里则生成having子句存放起来.将GeneratingWhere设为true
         *
         * @param jpqlContext 上下文
         * @param where       条件语句
         * @return 修改后的上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingWhere(JPQLContext jpqlContext, Where where) throws Exception {
                jpqlContext.generalJPQL.append(" FROM ").append(jpqlContext.entityClass.getSimpleName()).append(" ").append(jpqlContext.aliasMap.get(new Location(null)));
                if (null != where) {
                        if (where instanceof IExpression && DataUtils.expressionIsAggregate((IExpression) where)
                                || where instanceof Condition && null != DataUtils.getAggregate(where)) {
                                // where => having
                                jpqlContext.GeneratingWhere = false;
                                jpqlContext.havingClause = DSUtils.parseWhere(jpqlContext, where, parserPacket.andOrParser, parserPacket.conditionResolver, parserPacket.expressionResolver);
                                jpqlContext.RequireGroupBy = true;
                                jpqlContext.protectedConstantMap = jpqlContext.constantMap;
                                jpqlContext.constantMap = new ConstantMap();
                        }
                }
                jpqlContext.GeneratingWhere = true;
                return jpqlContext;
        }

        /**
         * 生成JOIN,WHERE,GROUP BY,HAVING子句
         *
         * @param jpqlContext 上下文
         * @param whereStr    条件语句字符串
         * @throws Exception 可能的异常
         */
        @Override
        public void afterParsingWhere(JPQLContext jpqlContext, Where where, final String whereStr) throws Exception {
                // JOIN
                jpqlContext.toDoAfterJPQLGeneration.add(new JPQLToDo<JPQLContext>() {
                        @Override
                        public void todo(JPQLContext jpqlContext) throws Exception {
                                StringBuilder sb = new StringBuilder();
                                for (Location joinLocation : jpqlContext.toJoin.keySet()) {
                                        sb.append(" JOIN ");
                                        if (joinLocation.getLocation().size() == 1) {
                                                sb.append(jpqlContext.aliasMap.get(new Location(null))).append(".");
                                        }
                                        sb.append(joinLocation.toString()).append(" ").append(jpqlContext.toJoin.get(joinLocation));

                                }
                                // WHERE
                                if (jpqlContext.protectedConstantMap == null) {
                                        if (whereStr != null && whereStr.trim().length() != 0) {
                                                sb.append(" WHERE ").append(whereStr);
                                        }
                                } else {
                                        jpqlContext.constantMap = jpqlContext.protectedConstantMap;
                                        jpqlContext.protectedConstantMap = null;
                                }
                                // Group BY
                                if (jpqlContext.RequireGroupBy && jpqlContext.selectNonAggregationAliases.size() != 0) {
                                        sb.append(" GROUP BY ");
                                        boolean isFirst = true;
                                        for (String s : jpqlContext.selectNonAggregationAliases) {
                                                if (isFirst) {
                                                        isFirst = false;
                                                } else {
                                                        sb.append(", ");
                                                }
                                                sb.append(s);
                                        }
                                }
                                // HAVING
                                if (jpqlContext.havingClause != null) {
                                        sb.append(" HAVING ").append(jpqlContext.havingClause);
                                }
                                jpqlContext.generalJPQL.append(sb.toString());
                        }
                });
        }

        /**
         * 直接返回上下文
         *
         * @param jpqlContext    上下文
         * @param queryParameter 查询参数
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingQueryParameter(JPQLContext jpqlContext, QueryParameter queryParameter) throws Exception {
                return jpqlContext;
        }

        /**
         * 不进行操作
         *
         * @param jpqlContext 上下文
         * @throws Exception jpa抛出的异常
         */
        @Override
        public void afterParsingQueryParameter(JPQLContext jpqlContext) throws Exception {
                // DO NOTHING
        }

        /**
         * 直接返回上下文
         *
         * @param jpqlContext             上下文
         * @param queryParameterWithFocus 带focus的参数
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingFocusPart(JPQLContext jpqlContext, QueryParameterWithFocus queryParameterWithFocus) throws Exception {
                return jpqlContext;
        }

        /**
         * 生成Query对象
         *
         * @param jpqlContext 上下文
         * @throws Exception jpa抛出的异常
         */
        @Override
        public void afterParsingFocusPart(JPQLContext jpqlContext) throws Exception {
                //jpqlContext.jpaQuery = jpqlContext.manager.createQuery(jpqlContext.frontQueries.toString() + jpqlContext.generalJPQL.toString());
        }

        /**
         * 直接返回上下文
         *
         * @param jpqlContext   上下文
         * @param updateEntries 更新项
         * @return 上下文
         * @throws Exception 可能的异常
         */
        @Override
        public JPQLContext beforeParsingUpdateEntries(JPQLContext jpqlContext, UpdateEntry[] updateEntries) throws Exception {
                return jpqlContext;
        }

        @Override
        public void destroy(EntityManager source) {
                source.close();
        }

        @Override
        public void afterParsingUpdateEntries(JPQLContext jpqlContext) throws Exception {
                // DO NOTHING
        }

        @Override
        public void setParserPacket(ParserPacket<JPQLContext, String, EntityManager> parserPacket) {
                this.parserPacket = parserPacket;
        }

        @Override
        public void setObjectResolver(ObjectResolver<JPQLContext, String> objectResolver) {

        }
}
